package kernel;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * System core
 *
 */
public class AsuvizCore {

	private Map<String, FiberSet> fiberSetIndex;
	private Map<String, Volume> volumeIndex;
	private static AsuvizCore instance;
	

	/**
	 * Gets an instance for the system
	 * @return
	 */
	public static AsuvizCore getInstance()
	{
		if(instance == null)
		{
			instance = new AsuvizCore();
		}
		return instance;
	}

	/**
	 * Constructor
	 */
	private AsuvizCore()
	{
		fiberSetIndex = new HashMap<String, FiberSet>();
		volumeIndex = new HashMap<String, Volume>();
	}
	
	/**
	 * Returns the maximum number of fibers that pass by a ROI
	 * @param fiberName name of the fiber set
	 * @return maximum
	 */
	public int getCounterRoiMax(String fiberName)
	{
		return fiberSetIndex.get(fiberName).getRoiCounterMax();
	}
	
	/**
	 * Returns the maximum number of ROIs that connects with another ROI
	 * @param fiberName name of the fiber set
	 * @return maximum
	 */
	public int getConnectivityCounterRoiMax(String fiberName)
	{
		return fiberSetIndex.get(fiberName).getRoiConnectivityCounterMax();
	}
	
	/**
	 * Returns a 3x3 int matrix that has the number of fibers that pass by a ROI
	 * @param fiberName name of the fiber set
	 * @return
	 */
	public int[][][] getCounterRoiMatrix(String fiberName)
	{
		FiberSet fiberSet = fiberSetIndex.get(fiberName);
		ROI[][][] roiMatrix = fiberSet.getRoiMatrix();
		
		int width = roiMatrix.length;
		int height = roiMatrix[0].length;
		int depth = roiMatrix[0][0].length;
		int[][][] counterMatrix = new int[width][height][depth];
		int max = 0;
		for(int i = 0; i < roiMatrix.length; i ++)
		{
			for(int j = 0; j < roiMatrix[0].length; j ++)
			{
				for(int k = 0; k < roiMatrix[0][0].length; k ++)
				{
					if(roiMatrix[i][j][k] != null)
					{
						counterMatrix[i][j][k] = roiMatrix[i][j][k].getFiberIds().size();
						if(counterMatrix[i][j][k] > max)
						{
							max = counterMatrix[i][j][k];
						}
					}
					else
					{
						counterMatrix[i][j][k] = 0;
					}
				}	
			}
		}
		fiberSet.setRoiCounterMax(max);
		return counterMatrix;
	}
	
	/**
	 * Returns a 3x3 int matrix that has the number of ROIs that has a connection with another ROI
	 * @param fiberName
	 * @return
	 */
	public int[][][] getConnectivityCounterRoiMatrix(String fiberName)
	{
		FiberSet fiberSet = fiberSetIndex.get(fiberName);
		ROI[][][] roiMatrix = fiberSet.getRoiMatrix();
		
		int width = roiMatrix.length;
		int height = roiMatrix[0].length;
		int depth = roiMatrix[0][0].length;
		int[][][] counterMatrix = new int[width][height][depth];
		int max = 0;
		for(int i = 0; i < roiMatrix.length; i ++)
		{
			for(int j = 0; j < roiMatrix[0].length; j ++)
			{
				for(int k = 0; k < roiMatrix[0][0].length; k ++)
				{
					ROI roi_ijk = roiMatrix[i][j][k];
					if(roi_ijk != null)
					{
						counterMatrix[i][j][k] = roi_ijk.getConnectivitySet().size();
						if(counterMatrix[i][j][k] > max)
						{
							max = counterMatrix[i][j][k];
						}
					}
					else
					{
						counterMatrix[i][j][k] = 0;
					}
				}	
			}
		}
		fiberSet.setRoiConnectivityCounterMax(max);
		return counterMatrix;
	}
	
	/**
	 * Generates and calculates the ROI matrix that contains the fibers that pass by a ROI
	 * @param fiberName name of the fiber set
	 * @param roiResolution resolution of the matrix in mm
	 */
	public void generateRoiMatrix(String fiberName, double roiResolution)
	{
		FiberSet fiberSet = fiberSetIndex.get(fiberName);
		fiberSet.createRoiMatrix(roiResolution);
		fiberSet.calculateRoiMatrix();
	}
	
	/**
	 * Calculates the connectivity matrix. It contains the ROIs that connects with another ROI <br>
	 * You must call generateRoiMatrix before calling this method
	 * @param fiberName name of the fiber set
	 */
	public void calculateConnectivityRoiMatrix(String fiberName)
	{
		FiberSet fiberSet = fiberSetIndex.get(fiberName);
		fiberSet.calculateConectivity3DMatrix();
	}
	
	public void calculateConnectivity2DMatrix(String fiberName, File file, int matrixWidth, int matrixHeight, int matrixDepth, int printType)
	{
		fiberSetIndex.get(fiberName).calculate2DConnectivityMatrix(matrixWidth, matrixHeight, matrixDepth, file, printType);
	}
	
	public void createVolumeFromFile(File file) throws Exception
	{
		Volume volume = new Volume();
		volume.readFile(file);
		volumeIndex.put(file.getAbsolutePath(), volume);
	}
	
	public ArrayList<Volume> getVolumes()
	{
		ArrayList<Volume> volumes = new ArrayList<Volume>();
		for(Map.Entry<String, Volume> entry : volumeIndex.entrySet())
		{
			volumes.add(entry.getValue());
		}
		return volumes;
	}
	
	/**
	 * Creates a new fiber set with the given new points in the file. The number of fibers remain the same
	 * @param file file path for the fiber set
	 * @param fiberSetName name for the fiber set
	 * @throws Exception if the file is not found
	 */
	public void createFiberFromFile(File file, String fiberSetName) throws Exception
	{
		BufferedReader reader = new BufferedReader(new FileReader(file));
		ArrayList<double[]> newPoints = new ArrayList<double[]>();
		String line = "";
		
		double minX = Double.MAX_VALUE;
		double minY = Double.MAX_VALUE;
		double minZ = Double.MAX_VALUE;
		
		double maxX = Double.MIN_VALUE;
		double maxY = Double.MIN_VALUE;
		double maxZ = Double.MIN_VALUE;
		while((line = reader.readLine()) != null)
		{
			String[] parse = line.split(",");
			if(parse.length % 3 != 0)
			{
				System.err.println("Line not read");
			}
			else
			{
				double x = Double.parseDouble(parse[0]);
				double y = Double.parseDouble(parse[1]);
				double z =  Double.parseDouble(parse[2]);
				newPoints.add(new double[]{ x,  y, z });
				
				if(x < minX)
				{
					minX = x;
				}
				if(x > maxX)
				{
					maxX = x;
				}
				if(y < minY)
				{
					minY = y;
				}
				if(y > maxY)
				{
					maxY = y;
				}
				if(z < minZ)
				{
					minZ = z;
				}
				if(z > maxZ)
				{
					maxZ = z;
				}
			}
		}
		reader.close();
		
		System.out.println("Bounds:\n\tXmin,Xmax: (" + minX + ", " + maxX + ")");
		System.out.println("\tYmin,Ymax: (" + minY + ", " + maxY + ")");
		System.out.println("\tZmin,Zmax: (" + minZ + ", " + maxZ + ")");
		try
		{
			fiberSetIndex.get(fiberSetName).createNewSet(newPoints);
		}catch(Exception e)
		{
			System.err.println("Fiber " + fiberSetName + " could not be loaded");
		}
	}

	/**
	 * Creates new fibers from a parameters map<br>
	 * Parameters map:<br>
	 * <ul>
	 * <li> FILEPATH: (String) file path for a fiber set
	 * <li> COLOR: (Color) color for the fiber set
	 * <li> RENDER_COUNT: (int) number of fibers to be render
	 * <li> RENDER_TYPE: (boolean[]) rendering type for the fiber set (LINES, POINTS, START_POINTS, END_POINTS)
	 * </ul>
	 * @param params parameters map
	 * @throws Exception
	 */
	public void addFiberSet(Map<Integer, Object> params) throws Exception
	{
		String fiberSetPath = (String)params.get(FiberSet.FILEPATH);
		Color fiberSetColor = (Color)params.get(FiberSet.COLOR);
		FiberSet fiberSet = new FiberSet();
		fiberSet.readFile(new File(fiberSetPath), fiberSetColor);
		fiberSet.setFiberRenderCount((Integer)params.get(FiberSet.RENDER_COUNT));
		fiberSet.setFiberRenderType((boolean[])params.get(FiberSet.RENDER_TYPE));

		int indexOfSeparator = fiberSetPath.lastIndexOf('\\');
		if(indexOfSeparator < 0)
		{
			indexOfSeparator = fiberSetPath.lastIndexOf('/');
		}

		fiberSetIndex.put(fiberSetPath.substring(indexOfSeparator), fiberSet);

	}
	
	/**
	 * Returns a 2x2 matrix with the connections between ROIs
	 * @param fiberSetName name of the fiber set
	 * @return
	 */
	public int[][] get2DIntegerConnectivityMatrix(String fiberSetName) throws Exception 
	{
		return null;
	}

	
	public void writeConnectivityMatrix(String fiberSetName, File file, int width, int height, int depth, boolean withHeader) throws Exception 
	{
		fiberSetIndex.get(fiberSetName).getIntConnectivityMatrix();
	}
	
	public int getConnectivityMatrixMax(String fiberSetName)
	{
		return fiberSetIndex.get(fiberSetName).getConnectivityMatrixMax();
	}
	
	public void writeConnectivityMatrix(int[][] connectivityMatrix, File file, boolean withHeaders) throws Exception
	{
		System.out.println("Writing Connectivity Matrix file " + file.getName());	
		System.out.println("With headers: " + withHeaders);
		int percentage = 0;
		
		BufferedWriter out = new BufferedWriter(new FileWriter(file));
		
		if(withHeaders)
		{
			out.write("ROIS");
			for(int i =0; i< connectivityMatrix.length; i++)
			{
				out.write(",<" + String.valueOf(i) + ">");
			}
			out.write("\n");
		}
		
		for(int i = 0; i < connectivityMatrix.length; i ++)
		{
			if((i+1)%(connectivityMatrix.length/10)==0)
			{
				percentage+=10;
				System.out.println(percentage + "% written. line " + (i+1) + " out of " + connectivityMatrix.length);
			}
			
			if(withHeaders)
			{
				out.write("<" + String.valueOf(i) + ">,");
			}
			for(int j = 0; j < connectivityMatrix[0].length; j ++)
			{
				int value = connectivityMatrix[i][j];
				
				if(j == 0)
				{
					out.write(String.valueOf(value));
				}
				else
				{
					out.write("," + value);	
				}
			}	
			out.write("\n");
		}
		out.close();
		System.out.println("End writing file.");
	}

	
	public void writeConnectivityMatrixBinary(int[][] connectivityMatrix, int threshold, File file) throws Exception
	{
		System.out.println("Writing file " + file.getName());	
		
		BufferedWriter out = new BufferedWriter(new FileWriter(file));
//		if(withHeader)
//		{
//			out.write("ROI_POS");
//			for(int i = 0; i < headers.size(); i++)
//			{
//				out.write("," + headers.get(i));
//			}
//			out.write("\n");
//		}
		for(int i = 0; i < connectivityMatrix.length; i ++)
		{
//			if(withHeader)
//			{
//				out.write(headers.get(i));
//			}
			for(int j = 0; j < connectivityMatrix[0].length; j ++)
			{
				int value = connectivityMatrix[i][j];
				if(value < threshold)
				{
					value = 0;
				}
				else
				{
					value = 1;
				}
				if(j == 0)
				{
					out.write(String.valueOf(value));
				}
				else
				{
					out.write("," + value);	
				}
				if(j == 0)
				{
					out.write(String.valueOf(connectivityMatrix[i][j]));
				}
				else
				{
					out.write("," + connectivityMatrix[i][j]);
				}
				
			}	
			out.write("\n");
		}
		out.close();
		System.out.println("End writing file.");

	}

	
	/**
	 * Creates a txt file with only the points of a fiber set
	 * @param fiberName name of the fiber set
	 * @param file file to be written
	 * @throws Exception
	 */
	public void writeFiberFile(String fiberName, File file) throws Exception
	{
		fiberSetIndex.get(fiberName).writeFile(file);
	}
	
	public void writeAllFiberFiles(File directory) throws Exception
	{
		int counter = 0;
		for(Map.Entry<String, FiberSet> entry : fiberSetIndex.entrySet())
		{
			FiberSet fiberSet = entry.getValue();
			File newFile = new File(directory.getAbsolutePath() + File.separatorChar + fiberSet.getName() + "_generated.txt");
			fiberSet.writeFile(newFile);
			counter++;
			System.out.println(counter + "Files created out of " + fiberSetIndex.size());
		}
	}

	/**
	 * Returns the map with the information of all the fiber sets that are in the system
	 * @return fiber set index
	 */
	public Map<String, FiberSet> getFiberSetIndex() {
		return fiberSetIndex;
	}

	/**
	 * Returns the width of the fiber set ROI matrix
	 * @param fiberSetName name of the fiber set
	 * @return
	 */
	public int getROIMatrixWidth(String fiberSetName) {
		return fiberSetIndex.get(fiberSetName).getROIWidth();
	}
	/**
	 * Returns the height of the fiber set ROI matrix
	 * @param fiberSetName name of the fiber set
	 * @return
	 */
	public int getROIMatrixHeight(String fiberSetName) {
		return fiberSetIndex.get(fiberSetName).getROIHeight();
	}
	/**
	 * Returns the depth of the fiber set ROI matrix
	 * @param fiberSetName name of the fiber set
	 * @return
	 */
	public int getROIMatrixDepth(String fiberSetName) {
		return fiberSetIndex.get(fiberSetName).getROIDepth();
	}

	public int[][][] getROIMatrixForFiber(String fiberName, int fiberIndex) {
		
		FiberSet fiberSet = fiberSetIndex.get(fiberName);
		ROI[][][] roiMatrix = fiberSet.getRoiMatrix();
		
		int width = roiMatrix.length;
		int height = roiMatrix[0].length;
		int depth = roiMatrix[0][0].length;
		int[][][] counterMatrix = new int[width][height][depth];
		int max = 0;
		for(int i = 0; i < roiMatrix.length; i ++)
		{
			for(int j = 0; j < roiMatrix[0].length; j ++)
			{
				for(int k = 0; k < roiMatrix[0][0].length; k ++)
				{
					ROI roi_ijk = roiMatrix[i][j][k];
					if(roi_ijk != null && roi_ijk.containFiber(fiberIndex))
					{
						counterMatrix[i][j][k] = 1;
					}
					else
					{
						counterMatrix[i][j][k] = 0;
					}
				}	
			}
		}
		fiberSet.setRoiConnectivityCounterMax(max);
		return counterMatrix;
	}

	public void createVTK(String fiberName) throws Exception
	{
		FiberSet fiberSet = fiberSetIndex.get(fiberName);
		if(fiberSet == null)
		{
			System.err.println("VTK file for " +fiberName + " not created");
		}
		else
		{
			File file = new File(fiberSet.getFilePath() + "_generated.vtk");
			fiberSet.writeVTKFile(file, true);
		}
	}

}
